<?php

/*

Desktopd Picture Server
Copyright (C) 2015  Desktopd Developer(s)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

https://www.gnu.org/licenses/agpl.html

*/


namespace Desktopd\PictureServer;

use Desktopd\PlatformTools\Random;
use Desktopd\PlatformTools\Loggable;
use Desktopd\PlatformTools\Logger;


/*
    Get a list of URIs for Wikimedia Commons pictures using some methods
    NOTE: Currently only feed sniffing is supported
    TODO: Get the descriptions of pictures
*/
class CommonsRetriever implements Loggable, \IteratorAggregate, \Countable {
    const CONFIG_PLAIN_SVG = 1; // default off
    const CONFIG_RESIZE_RASTER = 2; // default off
    
    const DEFAULT_TARGET_WIDTH = 2880;
    
    
    protected $loggers = array();
    
    protected $config;
    protected $downloader;
    protected $imageURIs = array(); // raw URIs from feed
    
    
    public function __construct (Downloader $downloader, $config = 0) {
        $this->downloader = $downloader;
        $this->config = (int) $config;
    }
    
    // get embedded thumbnail URIs in feeds
    public function sniffFeed ($feedURI) {
        $str = $this->downloader->requestResource($feedURI);
        if (!$str) return false;
        
        try {
            $doc = new \DOMDocument();
            $doc->loadXML($str);
            
            $text = array();
            
            $atom = $doc->getElementsByTagName('summary');
            if ($atom->length > 0) {
                // Atom
                foreach ($atom as $summary) {
                    $text[] = $summary->nodeValue;
                }
            } else {
                // RSS (?)
                $rss = $doc->getElementsByTagName('description');
                foreach ($rss as $description) {
                    $text[] = $description->nodeValue;
                }
            }
            
            $images = array();
            // XXX: Assuming that we have embedded HTML documents here
            foreach ($text as $html) {
                $doc = new \DOMDocument();
                $doc->loadHTML($html);
                $imageElements = $doc->getElementsByTagName('img');
                foreach ($imageElements as $imageElement) {
                    $src = $imageElement->getAttribute('src');
                    if ($src) {
                        $images[] = $src;
                        $this->imageURIs[] = $src;
                    }
                }
            }
            
            return count($images);
            
        } catch (\DOMException $e) {
            return false;
        }
    }
    
    // convert thumbnail URI to full size URI
    public function toFullImageURI($uri) {
        if (strpos("$uri", '/thumb/') === false) return $uri; // not supported URI
        $uri = explode('/', "$uri");
        array_pop($uri);
        $uri = implode('/', $uri);
        $uri = str_replace('/thumb/', '/', $uri);
        return $uri;
    }
    
    // convert full size URI to thumbnail URI
    public function toThumbURI($uri, $width) {
        if (!is_numeric($width)) {
            throw new \RuntimeException('Please specify the thumbnail size you want');
        }
        
        $width = (int) $width;
        $width = max(1, $width);
        
        if (strpos("$uri", '/thumb/') !== false) return $uri;
        $uri = str_replace('/commons/', '/commons/thumb/', $uri);
        $uri = explode('/', "$uri");
        $filename = array_pop($uri);
        $uri[] = $filename;
        if (substr($filename, -4) == '.svg') $filename .= '.png';
        $uri[] = "{$width}px-{$filename}";
        return implode('/', $uri);
    }
    
    public function getRawURIs () {
        return $this->imageURIs;
    }
    
    public function count () {
        return count($this->imageURIs);
    }
    
    // format URIs we have as needed and returns them
    public function getURIs ($targetWidth = self::DEFAULT_TARGET_WIDTH) {
        if (!is_numeric($targetWidth)) {
            $targetWidth = self::DEFAULT_TARGET_WIDTH;
        }
        
        $targetWidth = max(1, min(65536, (int) $targetWidth));
        
        $imageURIs = array();
        
        foreach ($this->imageURIs as $originalURI) {
            $extension = explode('.', $originalURI);
            $extension = strtolower(array_pop($extension));
            $isJPEG = ($extension == 'jpg' || $extension == 'jpeg');
            $isSVG = substr($originalURI, -8) == '.svg.png'
                || substr($originalURI, -4) == '.svg';
            
            $imageURI = $this->toFullImageURI($originalURI);
            $parts = explode('/', $imageURI);
            $filename = urldecode(end($parts));
            unset($parts);
            
            if ($isSVG && 0 == (self::CONFIG_PLAIN_SVG & $this->config)) {
                // We don't support SVG so we rely on Wikimedia's thumbnailing
                $imageURI = $this->toThumbURI($imageURI, $targetWidth);
            }
            
            if ((!$isSVG) &&  0 != (self::CONFIG_RESIZE_RASTER & $this->config)) {
                // force thumbnailing of non-SVG images
                $imageURI = $this->toThumbURI($imageURI, $targetWidth);
            }
            
            // assuming HTTP (may be processed with HTTPS Everywhere later)
            $imageURI = preg_replace('~^//~', 'http://', $imageURI);
            $imageURIs[] = $imageURI;
        }
        
        return $imageURIs;
    }
    
    public function downloadMetadata ($uri) {
        $original = $uri;
        $uri = $this->toFullImageURI($uri);
        if (!preg_match('~https?://upload.wikimedia.org/wikipedia/commons/./../(.+)~', $uri, $matches)) {
            $this->log("Not supported resource: $uri", Logger::WARNING);
            return false; // not supported
        }
        
        $name = $matches[1];
        $escaped = $this->downloader->encodeQuery($name); // URL query encode
        $api = "https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json&titles=File:$escaped";
        
        $this->log("(API) Requesting: $api", Logger::INFO);
        $json = $this->downloader->requestResource($api);
        if (false === $json) {
            $this->log("Failed to request: $api", Logger::WARNING);
            return false;
        }
        
        $json = json_decode($json, JSON_OBJECT_AS_ARRAY);
        if (!isset($json['query']['pages'])) {
            // Invalid data
            $this->log("(API) Invalid data returned", Logger::WARNING);
            return false;
        }
        
        $metadata = array();
        $printProperty = function ($name, $value) use (&$metadata) {
            $name = trim(strtolower($name));
            if ('categories' === $name) {
                $value = explode('|', "$value");
            } else {
                $value = preg_replace('~<!--.+?-->~', '', "$value");
                $value = preg_replace('~<?.+?\?>~', '', $value);
                $value = preg_replace('~<.+?>~', '', $value);
                $value = str_replace(array('&amp;', '&lt;', '&gt;', '&quot;', '&apos;'), array('&', '<', '>', '"', "'"), $value);
                $value = str_replace(array('>', '<', '&'), '', $value);
                $value = preg_replace('~\s+~', ' ', $value);
                $value = preg_replace('~^\s+|\s+$~', '', $value);
            }
            $metadata[$name] = $value;
        };
        
        foreach ($json['query']['pages'] as $page) {
            if (isset($page['title'])) {
                $printProperty('Title', $page['title']);
            }
            
            if (!isset($page['imageinfo'][0]['extmetadata'])) {
                continue;
            }
            
            $data = $page['imageinfo'][0]['extmetadata'];
            
            if (isset($data['ImageDescription']['value'])) {
                $printProperty('Description', $data['ImageDescription']['value']);
            }
            
            if (isset($data['Credit']['value'])) {
                $printProperty('Credit', $data['Credit']['value']);
            }
            
            if (isset($data['Artist']['value'])) {
                $printProperty('Artist', $data['Artist']['value']);
            }
            
            if (isset($data['UsageTerms']['value'])) {
                $printProperty('License', $data['UsageTerms']['value']);
            }
            
            if (isset($data['Categories']['value'])) {
                $printProperty('Categories', $data['Categories']['value']);
            }
            
        }
        
        
        $metadata['uri'] = $uri;
        $metadata['api'] = $api;
        
        return $metadata;
    }
    
    public function getIterator () {
        return new \ArrayIterator($this->getURIs());
    }
    
    public function hasLogger (Logger $logger) {
        return in_array($logger, $this->loggers, true);
    }
    
    public function addLogger (Logger $logger) {
        if (!in_array($logger, $this->loggers, true)) {
            $this->loggers[] = $logger;
        }
        
        return true;
    }
    
    public function removeLogger (Logger $logger) {
        foreach (array_keys($this->loggers, $logger, true) as $key) {
            unset($this->loggers[$key]);
        }
        
        return true;
    }
    
    protected function log ($message, $level = Logger::INFO) {
        foreach ($this->loggers as $logger) {
            $logger->log($message, $level);
        }
    }
}


// vim: ts=4 et ai

